//
//  EAGLView.m
//  spinnyglobe
//
//  Created by Anselm Hook on 9/4/08.
//  Copyright __MyCompanyName__ 2008. All rights reserved.
//

#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/EAGLDrawable.h>
#import "EAGLView.h"
#import "earth.h"


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#ifndef _Globe_Math_h
#define _Globe_Math_h

//#include <OpenGLES/gl.h>

//#define assert(x) {}

//Math types derived from the KempoApi tMath library
typedef union Tuple2f_t
    {
        struct
        {
            GLfloat X, Y;
        } s;
		
        GLfloat T[2];
    } Tuple2fT;      //A generic 2-element tuple that is represented by single-precision floating point x,y coordinates. 

typedef union Tuple3f_t
    {
        struct
        {
            GLfloat X, Y, Z;
        } s;
		
        GLfloat T[3];
    } Tuple3fT;      //A generic 3-element tuple that is represented by single precision-floating point x,y,z coordinates. 

typedef union Tuple4f_t
    {
        struct
        {
            GLfloat X, Y, Z, W;
        } s;
		
        GLfloat T[4];
    } Tuple4fT;      //A 4-element tuple represented by single-precision floating point x,y,z,w coordinates. 

#define M00 XX
#define M10 XY
#define M20 XZ
#define M01 YX
#define M11 YY
#define M21 YZ
#define M02 ZX
#define M12 ZY
#define M22 ZZ

typedef union Matrix3f_t
    {
		struct
		{
			// column major
			GLfloat XX; // union { GLfloat M00; GLfloat XX; GLfloat SX; };  //XAxis.X and Scale X
			GLfloat XY; // union { GLfloat M10; GLfloat XY;             } M10;  //XAxis.Y
			GLfloat XZ; // union { GLfloat M20; GLfloat XZ;             } M20;  //XAxis.Z
			GLfloat YX; // union { GLfloat M01; GLfloat YX;             } M01;  //YAxis.X
			GLfloat YY; // union { GLfloat M11; GLfloat YY; GLfloat SY; } M11;  //YAxis.Y and Scale Y
			GLfloat YZ; // union { GLfloat M21; GLfloat YZ;             } M21;  //YAxis.Z
			GLfloat ZX; // union { GLfloat M02; GLfloat ZX;             } M02;  //ZAxis.X
			GLfloat ZY; // union { GLfloat M12; GLfloat ZY;             } M12;  //ZAxis.Y
			GLfloat ZZ; // union { GLfloat M22; GLfloat ZZ; GLfloat SZ; } M22;  //ZAxis.Z and Scale Z
		} s;
		GLfloat M[9];
    } Matrix3fT;     //A single precision floating point 3 by 3 matrix. 

#define M30 XW
#define M31 YW
#define M32 ZW
#define M03 TX
#define M13 TY
#define M23 TZ
#define M33 TW

typedef union Matrix4f_t
    {
		struct
		{
			//column major
			GLfloat XX; // union { GLfloat M00; GLfloat XX; GLfloat SX; } union1;  //XAxis.X and Scale X
			GLfloat XY; // union { GLfloat M10; GLfloat XY;             } union2;  //XAxis.Y
			GLfloat XZ; // union { GLfloat M20; GLfloat XZ;             } union3;  //XAxis.Z
			GLfloat XW; // union { GLfloat M30; GLfloat XW;             } union4;  //XAxis.W
			GLfloat YX; // union { GLfloat M01; GLfloat YX;             } union5;  //YAxis.X
			GLfloat YY; // union M11 { GLfloat M11; GLfloat YY; GLfloat SY; } M11;  //YAxis.Y and Scale Y
			GLfloat YZ; // union { GLfloat M21; GLfloat YZ;             } union7;  //YAxis.Z
			GLfloat YW; // union { GLfloat M31; GLfloat YW;             } union8;  //YAxis.W
			GLfloat ZX; // union { GLfloat M02; GLfloat ZX;             } union9;  //ZAxis.X
			GLfloat ZY; // union { GLfloat M12; GLfloat ZY;             } union10;  //ZAxis.Y
			GLfloat ZZ; // union M22 { GLfloat M22; GLfloat ZZ; GLfloat SZ; } M22;  //ZAxis.Z and Scale Z
			GLfloat ZW; // union { GLfloat M32; GLfloat ZW;             } union12;  //ZAxis.W
			GLfloat TX; // union { GLfloat M03; GLfloat TX;             } union13;  //Trans.X
			GLfloat TY; // union { GLfloat M13; GLfloat TY;             } union14;  //Trans.Y
			GLfloat TZ; // union { GLfloat M23; GLfloat TZ;             } union15;  //Trans.Z
			GLfloat TW; // union { GLfloat M33; GLfloat TW; GLfloat SW; } union16;  //Trans.W and Scale W
		} s;
		GLfloat M[16];
    } Matrix4fT;     //A single precision floating point 4 by 4 matrix. 


//"Inherited" types
#define Point2fT    Tuple2fT   //A 2 element point that is represented by single precision floating point x,y coordinates. 

#define Quat4fT     Tuple4fT   //A 4 element unit quaternion represented by single precision floating point x,y,z,w coordinates. 

#define Vector2fT   Tuple2fT   //A 2-element vector that is represented by single-precision floating point x,y coordinates. 
#define Vector3fT   Tuple3fT   //A 3-element vector that is represented by single-precision floating point x,y,z coordinates. 

//Custom math, or speed overrides
#define MyFuncSqrt    sqrtf

//utility macros
//assuming IEEE-754(GLfloat), which i believe has max precision of 7 bits
# define Epsilon 1.0e-5

//Math functions

/**
 * Sets the value of this tuple to the vector sum of itself and tuple t1.
 * @param t1  the other tuple
 */
inline
static void Point2fAdd(Point2fT* NewObj, const Tuple2fT* t1)
{
	assert(NewObj && t1);
	
	NewObj->s.X += t1->s.X;
	NewObj->s.Y += t1->s.Y;
}

/**
 * Sets the value of this tuple to the vector difference of itself and tuple t1 (this = this - t1).
 * @param t1 the other tuple
 */
inline
static void Point2fSub(Point2fT* NewObj, const Tuple2fT* t1)
{
	assert(NewObj && t1);
	
	NewObj->s.X -= t1->s.X;
	NewObj->s.Y -= t1->s.Y;
}

/**
 * Sets this vector to be the vector cross product of vectors v1 and v2.
 * @param v1 the first vector
 * @param v2 the second vector
 */
inline
static void Vector3fCross(Vector3fT* NewObj, const Vector3fT* v1, const Vector3fT* v2)
{
	Vector3fT Result; //safe not to initialize
	
	assert(NewObj && v1 && v2);
	
	// store on stack once for aliasing-safty
	// i.e. safe when a.cross(a, b)
	
	Result.s.X = (v1->s.Y * v2->s.Z) - (v1->s.Z * v2->s.Y);
	Result.s.Y = (v1->s.Z * v2->s.X) - (v1->s.X * v2->s.Z);
	Result.s.Z = (v1->s.X * v2->s.Y) - (v1->s.Y * v2->s.X);
	
	//copy result back
	*NewObj = Result;
}

/**
 * Computes the dot product of the this vector and vector v1.
 * @param  v1 the other vector
 */
inline
static GLfloat Vector3fDot(const Vector3fT* NewObj, const Vector3fT* v1)
{
	assert(NewObj && v1);
	
	return  (NewObj->s.X * v1->s.X) +
	(NewObj->s.Y * v1->s.Y) +
	(NewObj->s.Z * v1->s.Z);
}

/**
 * Returns the squared length of this vector.
 * @return the squared length of this vector
 */
inline
static GLfloat Vector3fLengthSquared(const Vector3fT* NewObj)
{
	assert(NewObj);
	
	return  (NewObj->s.X * NewObj->s.X) +
	(NewObj->s.Y * NewObj->s.Y) +
	(NewObj->s.Z * NewObj->s.Z);
}

/**
 * Returns the length of this vector.
 * @return the length of this vector
 */
inline
static GLfloat Vector3fLength(const Vector3fT* NewObj)
{
	assert(NewObj);
	
	return MyFuncSqrt(Vector3fLengthSquared(NewObj));
}

inline
static void Matrix3fSetZero(Matrix3fT* NewObj)
{
	NewObj->s.M00 = NewObj->s.M01 = NewObj->s.M02 = 
	NewObj->s.M10 = NewObj->s.M11 = NewObj->s.M12 = 
	NewObj->s.M20 = NewObj->s.M21 = NewObj->s.M22 = 0.0f;
}

/**
 * Sets this Matrix3 to identity.
 */
inline
static void Matrix3fSetIdentity(Matrix3fT* NewObj)
{
	Matrix3fSetZero(NewObj);
	
	//then set diagonal as 1
	NewObj->s.M00 = 1.0f;
	NewObj->s.M11 = 1.0f;
	NewObj->s.M22 = 1.0f;
}

/**
 * Sets the value of this matrix to the matrix conversion of the
 * quaternion argument. 
 * @param q1 the quaternion to be converted 
 */
//$hack this can be optimized some(if s == 0)
inline
static void Matrix3fSetRotationFromQuat4f(Matrix3fT* NewObj, const Quat4fT* q1)
{
	GLfloat n, s;
	GLfloat xs, ys, zs;
	GLfloat wx, wy, wz;
	GLfloat xx, xy, xz;
	GLfloat yy, yz, zz;
	
	assert(NewObj && q1);
	
	n = (q1->s.X * q1->s.X) + (q1->s.Y * q1->s.Y) + (q1->s.Z * q1->s.Z) + (q1->s.W * q1->s.W);
	s = (n > 0.0f) ? (2.0f / n) : 0.0f;
	
	xs = q1->s.X * s;  ys = q1->s.Y * s;  zs = q1->s.Z * s;
	wx = q1->s.W * xs; wy = q1->s.W * ys; wz = q1->s.W * zs;
	xx = q1->s.X * xs; xy = q1->s.X * ys; xz = q1->s.X * zs;
	yy = q1->s.Y * ys; yz = q1->s.Y * zs; zz = q1->s.Z * zs;
	
	NewObj->s.XX = 1.0f - (yy + zz); NewObj->s.YX =         xy - wz;  NewObj->s.ZX =         xz + wy;
	NewObj->s.XY =         xy + wz;  NewObj->s.YY = 1.0f - (xx + zz); NewObj->s.ZY =         yz - wx;
	NewObj->s.XZ =         xz - wy;  NewObj->s.YZ =         yz + wx;  NewObj->s.ZZ = 1.0f - (xx + yy);
}

/**
 * Sets the value of this matrix to the result of multiplying itself
 * with matrix m1. 
 * @param m1 the other matrix 
 */
inline
static void Matrix3fMulMatrix3f(Matrix3fT* NewObj, const Matrix3fT* m1)
{
	Matrix3fT Result; //safe not to initialize
	
	assert(NewObj && m1);
	
	// alias-safe way.
	Result.s.M00 = (NewObj->s.M00 * m1->s.M00) + (NewObj->s.M01 * m1->s.M10) + (NewObj->s.M02 * m1->s.M20);
	Result.s.M01 = (NewObj->s.M00 * m1->s.M01) + (NewObj->s.M01 * m1->s.M11) + (NewObj->s.M02 * m1->s.M21);
	Result.s.M02 = (NewObj->s.M00 * m1->s.M02) + (NewObj->s.M01 * m1->s.M12) + (NewObj->s.M02 * m1->s.M22);
	
	Result.s.M10 = (NewObj->s.M10 * m1->s.M00) + (NewObj->s.M11 * m1->s.M10) + (NewObj->s.M12 * m1->s.M20);
	Result.s.M11 = (NewObj->s.M10 * m1->s.M01) + (NewObj->s.M11 * m1->s.M11) + (NewObj->s.M12 * m1->s.M21);
	Result.s.M12 = (NewObj->s.M10 * m1->s.M02) + (NewObj->s.M11 * m1->s.M12) + (NewObj->s.M12 * m1->s.M22);
	
	Result.s.M20 = (NewObj->s.M20 * m1->s.M00) + (NewObj->s.M21 * m1->s.M10) + (NewObj->s.M22 * m1->s.M20);
	Result.s.M21 = (NewObj->s.M20 * m1->s.M01) + (NewObj->s.M21 * m1->s.M11) + (NewObj->s.M22 * m1->s.M21);
	Result.s.M22 = (NewObj->s.M20 * m1->s.M02) + (NewObj->s.M21 * m1->s.M12) + (NewObj->s.M22 * m1->s.M22);
	
	//copy result back to this
	*NewObj = Result;
}

inline
static void Matrix4fSetRotationScaleFromMatrix4f(Matrix4fT* NewObj, const Matrix4fT* m1)
{
	assert(NewObj && m1);
	
	NewObj->s.XX = m1->s.XX; NewObj->s.YX = m1->s.YX; NewObj->s.ZX = m1->s.ZX;
	NewObj->s.XY = m1->s.XY; NewObj->s.YY = m1->s.YY; NewObj->s.ZY = m1->s.ZY;
	NewObj->s.XZ = m1->s.XZ; NewObj->s.YZ = m1->s.YZ; NewObj->s.ZZ = m1->s.ZZ;
}

/**
 * Performs SVD on this matrix and gets scale and rotation.
 * Rotation is placed into rot3, and rot4.
 * @param rot3 the rotation factor(Matrix3d). if null, ignored
 * @param rot4 the rotation factor(Matrix4) only upper 3x3 elements are changed. if null, ignored
 * @return scale factor
 */
inline
static GLfloat Matrix4fSVD(const Matrix4fT* NewObj, Matrix3fT* rot3, Matrix4fT* rot4)
{
	GLfloat s, n;
	
	assert(NewObj);
	
	// this is a simple svd.
	// Not complete but fast and reasonable.
	// See comment in Matrix3d.
	
	s = MyFuncSqrt(
				   ( (NewObj->s.XX * NewObj->s.XX) + (NewObj->s.XY * NewObj->s.XY) + (NewObj->s.XZ * NewObj->s.XZ) + 
					(NewObj->s.YX * NewObj->s.YX) + (NewObj->s.YY * NewObj->s.YY) + (NewObj->s.YZ * NewObj->s.YZ) +
					(NewObj->s.ZX * NewObj->s.ZX) + (NewObj->s.ZY * NewObj->s.ZY) + (NewObj->s.ZZ * NewObj->s.ZZ) ) / 3.0f );
	
	if (rot3)   //if pointer not null
	{
		//this->getRotationScale(rot3);
		rot3->s.XX = NewObj->s.XX; rot3->s.XY = NewObj->s.XY; rot3->s.XZ = NewObj->s.XZ;
		rot3->s.YX = NewObj->s.YX; rot3->s.YY = NewObj->s.YY; rot3->s.YZ = NewObj->s.YZ;
		rot3->s.ZX = NewObj->s.ZX; rot3->s.ZY = NewObj->s.ZY; rot3->s.ZZ = NewObj->s.ZZ;
		
		// zero-div may occur.
		
		n = 1.0f / MyFuncSqrt( (NewObj->s.XX * NewObj->s.XX) +
							  (NewObj->s.XY * NewObj->s.XY) +
							  (NewObj->s.XZ * NewObj->s.XZ) );
		rot3->s.XX *= n;
		rot3->s.XY *= n;
		rot3->s.XZ *= n;
		
		n = 1.0f / MyFuncSqrt( (NewObj->s.YX * NewObj->s.YX) +
							  (NewObj->s.YY * NewObj->s.YY) +
							  (NewObj->s.YZ * NewObj->s.YZ) );
		rot3->s.YX *= n;
		rot3->s.YY *= n;
		rot3->s.YZ *= n;
		
		n = 1.0f / MyFuncSqrt( (NewObj->s.ZX * NewObj->s.ZX) +
							  (NewObj->s.ZY * NewObj->s.ZY) +
							  (NewObj->s.ZZ * NewObj->s.ZZ) );
		rot3->s.ZX *= n;
		rot3->s.ZY *= n;
		rot3->s.ZZ *= n;
	}
	
	if (rot4)   //if pointer not null
	{
		if (rot4 != NewObj)
		{
			Matrix4fSetRotationScaleFromMatrix4f(rot4, NewObj);  // private method
		}
		
		// zero-div may occur.
		
		n = 1.0f / MyFuncSqrt( (NewObj->s.XX * NewObj->s.XX) +
							  (NewObj->s.XY * NewObj->s.XY) +
							  (NewObj->s.XZ * NewObj->s.XZ) );
		rot4->s.XX *= n;
		rot4->s.XY *= n;
		rot4->s.XZ *= n;
		
		n = 1.0f / MyFuncSqrt( (NewObj->s.YX * NewObj->s.YX) +
							  (NewObj->s.YY * NewObj->s.YY) +
							  (NewObj->s.YZ * NewObj->s.YZ) );
		rot4->s.YX *= n;
		rot4->s.YY *= n;
		rot4->s.YZ *= n;
		
		n = 1.0f / MyFuncSqrt( (NewObj->s.ZX * NewObj->s.ZX) +
							  (NewObj->s.ZY * NewObj->s.ZY) +
							  (NewObj->s.ZZ * NewObj->s.ZZ) );
		rot4->s.ZX *= n;
		rot4->s.ZY *= n;
		rot4->s.ZZ *= n;
	}
	
	return s;
}

inline
static void Matrix4fSetRotationScaleFromMatrix3f(Matrix4fT* NewObj, const Matrix3fT* m1)
{
	assert(NewObj && m1);
	
	NewObj->s.XX = m1->s.XX; NewObj->s.YX = m1->s.YX; NewObj->s.ZX = m1->s.ZX;
	NewObj->s.XY = m1->s.XY; NewObj->s.YY = m1->s.YY; NewObj->s.ZY = m1->s.ZY;
	NewObj->s.XZ = m1->s.XZ; NewObj->s.YZ = m1->s.YZ; NewObj->s.ZZ = m1->s.ZZ;
}

inline
static void Matrix4fMulRotationScale(Matrix4fT* NewObj, GLfloat scale)
{
	assert(NewObj);
	
	NewObj->s.XX *= scale; NewObj->s.YX *= scale; NewObj->s.ZX *= scale;
	NewObj->s.XY *= scale; NewObj->s.YY *= scale; NewObj->s.ZY *= scale;
	NewObj->s.XZ *= scale; NewObj->s.YZ *= scale; NewObj->s.ZZ *= scale;
}

/**
 * Sets the rotational component (upper 3x3) of this matrix to the matrix
 * values in the T precision Matrix3d argument; the other elements of
 * this matrix are unchanged; a singular value decomposition is performed
 * on this object's upper 3x3 matrix to factor out the scale, then this
 * object's upper 3x3 matrix components are replaced by the passed rotation
 * components, and then the scale is reapplied to the rotational
 * components.
 * @param m1 T precision 3x3 matrix
 */
inline
static void Matrix4fSetRotationFromMatrix3f(Matrix4fT* NewObj, const Matrix3fT* m1)
{
	GLfloat scale;
	
	assert(NewObj && m1);
	
	scale = Matrix4fSVD(NewObj, 0, 0);
	
	Matrix4fSetRotationScaleFromMatrix3f(NewObj, m1);
	Matrix4fMulRotationScale(NewObj, scale);
}

#endif


///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// ********************************
// ARCBALL STATE 
// ******************************** 

Vector3fT   StVec;          //Saved click vector
Vector3fT   EnVec;          //Saved drag vector
GLfloat     AdjustWidth;    //Mouse bounds width
GLfloat     AdjustHeight;   //Mouse bounds height

Matrix4fT   Transform   = {  1.0f,  0.0f,  0.0f,  0.0f,
0.0f,  1.0f,  0.0f,  0.0f,
0.0f,  0.0f,  1.0f,  0.0f,
0.0f,  0.0f,  0.0f,  1.0f };

Matrix3fT   LastRot     = {  1.0f,  0.0f,  0.0f,
0.0f,  1.0f,  0.0f,
0.0f,  0.0f,  1.0f };

Matrix3fT   ThisRot     = {  1.0f,  0.0f,  0.0f,
0.0f,  1.0f,  0.0f,
0.0f,  0.0f,  1.0f  };

int isDragging = 0;
Point2fT MousePt;

//Arcball sphere constants:
//Diameter is       2.0f
//Radius is         1.0f
//Radius squared is 1.0f

// ********************************
// ARCBALL LOGIC
// ******************************** 

void setBounds(GLfloat NewWidth, GLfloat NewHeight)
{
	assert((NewWidth > 1.0f) && (NewHeight > 1.0f));
	AdjustWidth  = 1.0f / ((NewWidth  - 1.0f) * 0.5f);
	AdjustHeight = 1.0f / ((NewHeight - 1.0f) * 0.5f);
}

void ArcBall_mapToSphere(Point2fT* NewPt, Vector3fT* NewVec) 
{
    Point2fT TempPt;
    GLfloat length;
	
    //Copy paramter into temp point
    TempPt = *NewPt;
	
    //Adjust point coords and scale down to range of [-1 ... 1]
    TempPt.s.X  =        (TempPt.s.X * AdjustWidth)  - 1.0f;
    TempPt.s.Y  = 1.0f - (TempPt.s.Y * AdjustHeight);
	
    //Compute the square of the length of the vector to the point from the center
    length      = (TempPt.s.X * TempPt.s.X) + (TempPt.s.Y * TempPt.s.Y);
	
    //If the point is mapped outside of the sphere... (length > radius squared)
    if (length > 1.0f)
    {
        GLfloat norm;
		
        //Compute a normalizing factor (radius / sqrt(length))
        norm    = 1.0f / MyFuncSqrt(length);
		
        //Return the "normalized" vector, a point on the sphere
        NewVec->s.X = TempPt.s.X * norm;
        NewVec->s.Y = TempPt.s.Y * norm;
        NewVec->s.Z = 0.0f;
    }
    else    //Else it's on the inside
    {
        //Return a vector to a point mapped inside the sphere sqrt - length)
        NewVec->s.X = TempPt.s.X;
        NewVec->s.Y = TempPt.s.Y;
        NewVec->s.Z = MyFuncSqrt(1.0f - length);
    }
}

//Create/Destroy
void ArcBall_initialize(int NewWidth, int NewHeight)
{
    //Clear initial values
    StVec.s.X     =
    StVec.s.Y     = 
    StVec.s.Z     = 
	
    EnVec.s.X     =
    EnVec.s.Y     = 
    EnVec.s.Z     = 0.0f;
	
    //Set initial bounds
    setBounds( 1.0f * NewWidth, 1.0f * NewHeight);
}

void ArcBall_click(Point2fT* NewPt)
{
    //Map the point to the sphere
    ArcBall_mapToSphere(NewPt, &StVec);
}

void ArcBall_drag(Point2fT* NewPt, Quat4fT* NewRot)
{
    //Map the point to the sphere
    ArcBall_mapToSphere(NewPt, &EnVec);
	
    //Return the quaternion equivalent to the rotation
    if (NewRot)
    {
        Vector3fT  Perp;
		
        //Compute the vector perpendicular to the begin and end vectors
        Vector3fCross(&Perp, &StVec, &EnVec);
		
        //Compute the length of the perpendicular vector
        if (Vector3fLength(&Perp) > Epsilon)    //if its non-zero
        {
            // return the perpendicular vector as the transform after all
            NewRot->s.X = Perp.s.X;
            NewRot->s.Y = Perp.s.Y;
            NewRot->s.Z = Perp.s.Z;
            // w is cosine (theta / 2), where theta is rotation angle
            NewRot->s.W= Vector3fDot(&StVec, &EnVec);
        }
        else                                    //if its zero
        {
            //begin and end vectors coincide, so return an identity transform
            NewRot->s.X = 
            NewRot->s.Y = 
            NewRot->s.Z = 
            NewRot->s.W = 0.0f;
        }
    }
}

float* ArcBall_update(int x,int y,int down) {
    Quat4fT q;
    MousePt.s.X = x;
    MousePt.s.Y = y;
    if (!down) {
        isDragging = 0;
    }
    else if(!isDragging) {
        isDragging = 1;
        LastRot = ThisRot;
        ArcBall_click(&MousePt);
    }
    else {
        ArcBall_drag(&MousePt,&q);
        Matrix3fSetRotationFromQuat4f(&ThisRot, &q);
        Matrix3fMulMatrix3f(&ThisRot, &LastRot);
        Matrix4fSetRotationFromMatrix3f(&Transform, &ThisRot);
    }
    return (float*)&Transform;
}



///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#define USE_DEPTH_BUFFER 0

// A class extension to declare private methods
@interface EAGLView ()

@property (nonatomic, retain) EAGLContext *context;
@property (nonatomic, assign) NSTimer *animationTimer;

- (BOOL) createFramebuffer;
- (void) destroyFramebuffer;
- (void) drawSphere;
- (void) makeTextures;

@end


@implementation EAGLView

@synthesize context;
@synthesize animationTimer;
@synthesize animationInterval;


// You must implement this
+ (Class)layerClass {
	return [CAEAGLLayer class];
}


//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
- (id)initWithCoder:(NSCoder*)coder {

	if ((self = [super initWithCoder:coder])) {
		// Get the layer
		CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
		
		eaglLayer.opaque = YES;
		eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
		   [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
		
		context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
		
		if (!context || ![EAGLContext setCurrentContext:context]) {
			[self release];
			return nil;
		}
		
		animationInterval = 1.0 / 60.0;
	}
	return self;
}

GLfloat colors[] = {
	1,0,0,1,
	0,1,0,1,
	0,0,1,1,
};

GLfloat glvertices[] = {
	0,0,0,
	0,0,0,
	0,0,0,
	0,0,0
};

GLfloat gltextures[] = {
	0,0,
	0,0,
	0,0,
	0,0,
};

int is_flat=0;
float surface=1;
float zoom=0;
float longitude=0;
float latitude=0;
int pw=512;
int ph=512;
int tilepw=256;
int tileph=128;
GLuint texture[1];
extern GLuint texture_earth[];
int initialized_textures = 0;

- (void)makeTextures {
	/*
	GLTextureHelper helper(1024, 1024);
	glBindTexture(GL_TEXTURE_2D, texture[0]);
	glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
	helper.LoadTexture("earth.png"); 
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	 */
	int w = 256;
	int h = 128;
	glGenTextures(1,&texture[0]);
	glBindTexture(GL_TEXTURE_2D,texture[0]);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
	glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
	glTexImage2D(GL_TEXTURE_2D,0,GL_RGB,w,h,0,GL_RGB,GL_UNSIGNED_BYTE,texture_earth);

 }

- (void)drawSphere {

 // size of world
 float wwidth = 360.0;
 float wheight = 180.0;
 
 // what is the world space tile ratio?
 int tileratio = tileph/tilepw;
 
 // how many tiles would we like to use to tile the world at this zoom?
 int tiles = (int)pow(2,zoom);
 
 // how big are the tiles in world space?
 float tilegw = wwidth/tiles;
 float tilegh = wheight/tiles;
 
 // get a positive tile index
 int tilex = (int)round((longitude+(wwidth/2))/tilegw);
 int tiley = (int)round((latitude+(wheight/2))/tilegh);
 
 // pick a patch that defines the visible mesh extent at the chosen point... can wraparound axes.
 int LT = (tilex-(int)round(pw/tilepw/2));
 int RT = (tilex+(int)round(pw/tilepw/2));
 int TP = (tiley-(int)round(ph/tileph/2));
 int BT = (tiley+(int)round(ph/tileph/2));
 
 // hack: due to wraparound at low zoom just show the whole planet as one tile
 if(zoom < 4) {
	tiles = 1;
	tilex = 0;
	tiley = 0;
	LT = 0;
	RT = 1;
	TP = 0;
	BT = 1;
 }
 
 // decide on an integer subtiling regimen to provide a nice sense of curvature for the mesh
 int subtile = (is_flat || tiles > 16 ) ? 1 : ((int)ceil(16/tiles));
 
 // make vertices for the mesh; as a series of horizontal strings of vertices
 //float m = 0;
 int i;
 int j;
 float x;
 float y;
 float z = 0;
 float t;
 float rad1;
 float rad2;
 float *v;
 int vertex_row_length = (RT-LT)*subtile+1;
 int vertex_rows_total = (BT-TP)*subtile+1;
 int vertices_length = vertex_row_length * vertex_rows_total * 3;
 GLfloat vertices[vertices_length];
 //int test = 0;
 //if(test){glBegin(GL_POINTS);}
 for(j=0;j<vertex_rows_total;j++) {							// visit vertical range of subtile EDGES
	if(!is_flat) {
		rad1 = 1.0f*(j+TP)/(tiles*subtile);					// fractional degree along the arc of latitude
		y = -surface*cos(rad1*3.14159265f);					// might as well calculate y now
		t = surface*sin(rad1*3.14159265f);					// convenience
	}
	v = 0;													// reset vertex to deal with poles
	for(i=0;i<vertex_row_length;i++) {						// visit horizontal range of subtile EDGES!
		if(is_flat) {
			x = surface/wwidth*((i+LT)/subtile*tilegw-(wwidth/2));// scale to scale size and center
			y = surface/wheight*((j+TP)/subtile*tilegh-(wheight/2))*tileratio;
		} else {
			rad2 = 2.0f*(i+LT)/(tiles*subtile);				// fractional degree along arc of longitude
			x = -t*sin(rad2*3.14159265f);
			z = t*cos(rad2*3.14159265f);
		}
		// optional optimization
		//if(is_flat==0 && (rad1==0||rad1>=1 && v!=null) {  // only one vertex at pole
		//  TODO should really enable this to prevent seams but it requires tracking vertex_rows...
		//} else
		{
			vertices[(j*vertex_row_length+i)*3+0] = x;
			vertices[(j*vertex_row_length+i)*3+1] = y;
			vertices[(j*vertex_row_length+i)*3+2] = z;
			//if(test){glColor3f(m*2,1.0f,1.0f);glVertex3f(x+m,y+m,z+m); m = m + 0.001; }
		}
	}
 }
 //if(test){glEnd();return;}

 // make an idealized uv subdivision of a tile
 float uv_rows[(subtile+1)*(subtile+1)*2];
 for(j=0;j<=subtile;j++) {							// visit vertical subrange EDGES
	for(i=0;i<=subtile;i++) {						// visit horizontal subrange EDGES
		uv_rows[(j*subtile+j+i)*2+0]=1.0f*i/subtile;
		uv_rows[(j*subtile+j+i)*2+1]=1.0f*j/subtile;
	}
 }

 GLuint faces[(vertex_rows_total-1)*(vertex_row_length-1)];

 //glBegin(GL_QUADS);
 glEnableClientState(GL_VERTEX_ARRAY);
 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
 glEnable(GL_TEXTURE_2D);
 glBindTexture(GL_TEXTURE_2D,texture[0]);

 for(j=0;j<vertex_rows_total-1;j++) {				// visit vertical range of tiles
	for(i=0;i<vertex_row_length-1;i++) {				// visit horizontal range of tiles
		
		
		x = vertices[(j*vertex_row_length+i)*3+0];
		y = vertices[(j*vertex_row_length+i)*3+1];
		z = vertices[(j*vertex_row_length+i)*3+2];
		glvertices[0*3+0]=x;glvertices[0*3+1]=y;glvertices[0*3+2]=z;
		gltextures[0*2+0]= uv_rows[((j%subtile)*(subtile+1)+(i%subtile))*2+0];
		gltextures[0*2+1]= uv_rows[((j%subtile)*(subtile+1)+(i%subtile))*2+1];
		faces[0]=j*vertex_row_length+i;
		
		x = vertices[(j*vertex_row_length+i+1)*3+0];
		y = vertices[(j*vertex_row_length+i+1)*3+1];
		z = vertices[(j*vertex_row_length+i+1)*3+2];
		glvertices[1*3+0]=x;glvertices[1*3+1]=y;glvertices[1*3+2]=z;
		gltextures[1*2+0]= uv_rows[((j%subtile)*(subtile+1)+(i%subtile+1))*2+0];
		gltextures[1*2+1]= uv_rows[((j%subtile)*(subtile+1)+(i%subtile+1))*2+1];
		faces[1]=j*vertex_row_length+i+1;
		
		x = vertices[(j*vertex_row_length+vertex_row_length+i)*3+0+3];
		y = vertices[(j*vertex_row_length+vertex_row_length+i)*3+1+3];
		z = vertices[(j*vertex_row_length+vertex_row_length+i)*3+2+3];
		glvertices[2*3+0]=x;glvertices[2*3+1]=y;glvertices[2*3+2]=z;
		gltextures[2*2+0]= uv_rows[((j%subtile+1)*(subtile+1)+(i%subtile+1))*2+0];
		gltextures[2*2+1]= uv_rows[((j%subtile+1)*(subtile+1)+(i%subtile+1))*2+1];
		faces[2]=j*vertex_row_length+vertex_row_length+i+1;
		
		x = vertices[(j*vertex_row_length+vertex_row_length+i)*3+0];
		y = vertices[(j*vertex_row_length+vertex_row_length+i)*3+1];
		z = vertices[(j*vertex_row_length+vertex_row_length+i)*3+2];
		glvertices[3*3+0]=x;glvertices[3*3+1]=y;glvertices[3*3+2]=z;
		gltextures[3*2+0]= uv_rows[((j%subtile+1)*(subtile+1)+(i%subtile))*2+0];
		gltextures[3*2+1]= uv_rows[((j%subtile+1)*(subtile+1)+(i%subtile))*2+1];
		faces[3]=j*vertex_row_length+vertex_row_length+i;
		
		// xxx todo remove - i threw this in because i can't get glDrawElements() below to behave...
		// basically i'm just flipping the vertex pair so i can use a triangle strip
		x = vertices[(j*vertex_row_length+vertex_row_length+i)*3+0];
		y = vertices[(j*vertex_row_length+vertex_row_length+i)*3+1];
		z = vertices[(j*vertex_row_length+vertex_row_length+i)*3+2];
		glvertices[2*3+0]=x;glvertices[2*3+1]=y;glvertices[2*3+2]=z;
		gltextures[2*2+0]= uv_rows[((j%subtile+1)*(subtile+1)+(i%subtile))*2+0];
		gltextures[2*2+1]= uv_rows[((j%subtile+1)*(subtile+1)+(i%subtile))*2+1];
		faces[2]=j*vertex_row_length+vertex_row_length+i+1;
		
		x = vertices[(j*vertex_row_length+vertex_row_length+i)*3+0+3];
		y = vertices[(j*vertex_row_length+vertex_row_length+i)*3+1+3];
		z = vertices[(j*vertex_row_length+vertex_row_length+i)*3+2+3];
		glvertices[3*3+0]=x;glvertices[3*3+1]=y;glvertices[3*3+2]=z;
		gltextures[3*2+0]= uv_rows[((j%subtile+1)*(subtile+1)+(i%subtile+1))*2+0];
		gltextures[3*2+1]= uv_rows[((j%subtile+1)*(subtile+1)+(i%subtile+1))*2+1];
		faces[3]=j*vertex_row_length+vertex_row_length+i;
		
		glTexCoordPointer(2,GL_FLOAT,0,gltextures);
		if(0) {
			// fails???
			glVertexPointer(3,GL_FLOAT,0,vertices);
			glDrawElements(GL_TRIANGLES,1,GL_FIXED,faces);
		} else {
			glVertexPointer(3,GL_FLOAT,0,glvertices);
			glDrawArrays(GL_TRIANGLE_STRIP,0,4);
		}
	}
 }

 glDisableClientState(GL_VERTEX_ARRAY);
 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
 glDisable(GL_TEXTURE_2D);

}



/*
 
 - (void) mouseDown:(void*)event {
 CGRect loc = GSEventGetLocationInWindow(event);
 globe_event(GLOBE_MOUSEDOWN,loc.origin.x,loc.origin.y);
 }
 
 - (void) mouseDragged:(void*)event {
 CGRect loc = GSEventGetLocationInWindow(event);
 globe_event(GLOBE_MOUSEDRAG,loc.origin.x,loc.origin.y);
 }
 
 - (void) mouseUp:(void*)event {
 CGRect loc = GSEventGetLocationInWindow(event);
 globe_event(GLOBE_MOUSEUP,loc.origin.x,loc.origin.y);
 }
 
 
 
 ArcBall_initialize(w,h);
 initialized = 1;
 }
 
 void globe_event(int event,int x,int y) {
 if(!initialized) {
 return;
 }
 if(1) {
 float* mat = ArcBall_update(x,y,
 (event == GLOBE_MOUSEDRAG || event == GLOBE_MOUSEDOWN )
 ? 1 : 0);
 glLoadIdentity();
 glTranslatef(0,0,-1);
 //glRotatef(rtri,rtri,rtri,0);
 glMultMatrixf(mat);
 rtri+=0.3f;
 
 */


- (void)drawView {

	[EAGLContext setCurrentContext:context];

	if( initialized_textures == 0 ) {
		[self makeTextures];
		initialized_textures = 1;
	}

	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
	glViewport(0, 0, backingWidth, backingHeight);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glTranslatef(0,0,-1);
	//glMultMatrixf(mat);
	//rtri+=0.3f;

	glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	[self drawSphere];

	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	[context presentRenderbuffer:GL_RENDERBUFFER_OES];

}	


- (void)layoutSubviews {
	[EAGLContext setCurrentContext:context];
	[self destroyFramebuffer];
	[self createFramebuffer];
	[self drawView];
}


- (BOOL)createFramebuffer {
	
	glGenFramebuffersOES(1, &viewFramebuffer);
	glGenRenderbuffersOES(1, &viewRenderbuffer);
	
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	[context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
	glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
	
	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
	glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
	
	if (USE_DEPTH_BUFFER) {
		glGenRenderbuffersOES(1, &depthRenderbuffer);
		glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
		glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
		glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
	}

	if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
		NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
		return NO;
	}
	
	return YES;
}


- (void)destroyFramebuffer {
	
	glDeleteFramebuffersOES(1, &viewFramebuffer);
	viewFramebuffer = 0;
	glDeleteRenderbuffersOES(1, &viewRenderbuffer);
	viewRenderbuffer = 0;
	
	if(depthRenderbuffer) {
		glDeleteRenderbuffersOES(1, &depthRenderbuffer);
		depthRenderbuffer = 0;
	}
}


- (void)startAnimation {
	self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
}


- (void)stopAnimation {
	self.animationTimer = nil;
}


- (void)setAnimationTimer:(NSTimer *)newTimer {
	[animationTimer invalidate];
	animationTimer = newTimer;
}


- (void)setAnimationInterval:(NSTimeInterval)interval {
	
	animationInterval = interval;
	if (animationTimer) {
		[self stopAnimation];
		[self startAnimation];
	}
}


- (void)dealloc {
	
	[self stopAnimation];
	
	if ([EAGLContext currentContext] == context) {
		[EAGLContext setCurrentContext:nil];
	}
	
	[context release];	
	[super dealloc];
}

@end
